Exam 2 Question 1

This notebook should be read in parallel to Section 1 of the document E2_ANDREAS MAVROCORDATOS_REPORT. Please note that the computation time in this notebook may not reflect exactly the computation time of the report due to multiple runs of the notebook (the running time trends however are more accurately reflected in the report).

Import libraries¶

In [1]:
# Import libraries for data manipulation
import pandas as pd
import numpy as np
from numpy import *

# Import libraries for visualization
import plotly.graph_objs as go
import plotly.figure_factory as ff
import matplotlib.pyplot as plt

# Import libraries for statistical analysis
import scipy.stats as stats
from scipy.stats import norm
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.stats.diagnostic import acorr_ljungbox

import time

import warnings
warnings.filterwarnings('ignore')

Table of Content¶

  • 1. Path Generation
  • 2. Statistical Validation
    • 2.1. Distribution of returns
    • 2.2. Autocorrelation test
  • 3. Sampling and Averaging
  • 4. Asian Option Payoff
  • 4. Asian Option Payoff
    • 4.1 Fixed Strike Price
    • 4.2 Floating Strike Price
    • 4.3 Impact of Volatility on Asian options
    • 4.4 Impact of Return on Asian options
    • 4.5 Impact of Time Horizon on Asian options
    • 4.6 In-The-Money vs Out-of-The-Money options
  • 5. Appendix
    • 5.1 Visual impact of changing periods for discrete sampling
    • 5.2 Payoff and computation impact of changing the number of discrete time periods
    • 5.3 Impact of changing number of simulations

Path generation ¶

In [2]:
""""    
Below, three functions are defined to simulate the paths of stocks based on three methodologies:
- Closed form solution for GBM paths
- Euler-Maruyam for GBM paths
- Milstein Scheme for GBM paths

Each of the functions below have the following attributes:

n_sims:           number of simulations
S0:               initial stock price
r:                constant risk-free interest rate
sigma:            volatilty
T:                time to expiry
t_steps:          number of time steps 

In addition, the E-M and Milstein Scheme have an additional parameter called the sampling_frequency.
This parameter has default value of 252 x T (i.e. continuous sample for 252 business days during T number of years)
"""

def closed_form(S0, rfr, vol, horizon, timesteps, n_sims):
    
    """This is a function returns the closed-form solution for GBM paths"""
    
    np.random.seed(1)                      # Set random seed for reproducibility
    dt = horizon / timesteps                  # Calculate time step
    t = np.arange(dt, horizon + dt, dt)       # Create an array of time points
    closed_paths = []                         # Initialize paths array
    
    # Generate paths
    for i in range(n_sims):                                          # Loop over each simulation
        increments = np.sqrt(dt) * np.random.randn(timesteps)        # Generate random increments for Brownian motion
        brownian_motion = np.cumsum(increments)                      # Calculate Brownian motion path
        
        # Calculate the exact solution path (start at S0) based on formula from tutorial and lectures
        path = np.concatenate(([S0], S0 * np.exp((rfr - 0.5 * vol**2) * t + vol * brownian_motion)))
        closed_paths.append(path)
    
    return closed_paths
        

def euler_maruyama(S0, rfr, vol, horizon, timesteps, n_sims):
    
    """This is a function returns the solution based on the Euler-Maruyama method for GBM paths"""
        
    np.random.seed(1)                        # Set seed for reproducibility
    dt = horizon / timesteps                    # Calculate time step
    t = np.arange(dt, horizon + dt, dt)         # Create an array of time points
    
    EM_paths = []
    
    # Generate paths
    for i in range(n_sims):
        increments = np.sqrt(dt) * np.random.randn(timesteps)  # Generate random increments for Brownian motion
        brownian_motion = np.cumsum(increments)                # Calculate Brownian motion path
        
        em_path = [S0]                                         # Initialize the path with the initial value
        current_value = S0                                     # Set the current value to the initial value
        
        for j in range(len(increments)):
            em_increment = rfr * current_value * dt + vol * current_value * increments[j]  # Calculate the E-M increment
            current_value += em_increment                                                  # Update the current value
            em_path.append(current_value)                                                  # Add the current value to the path
        
        EM_paths.append(em_path)

    return EM_paths



def milstein_paths(S0, rfr, vol, horizon, timesteps, n_sims):
    
    """This is a function returns the solution based on the Milstein Scheme method for GBM paths"""
    
    np.random.seed(1)                  # Set seed for reproducibility
    dt = horizon / timesteps              # Calculate time step
    t = np.arange(dt, horizon + dt, dt)   # Create an array of time points
    
    MS_paths = []
    
    # Generate paths
    for i in range(n_sims):                                    # Loop over each simulation
        increments = np.sqrt(dt) * np.random.randn(timesteps)  # Generate random increments for Brownian motion
        brownian_motion = np.cumsum(increments)                # Calculate Brownian motion path
        
        milstein_path = [S0]                                   # Initialize the path with the initial value
        current_value = S0                                     # Set the current value to the initial value
        
        for j in range(len(increments)):
            # Calculate the Milstein increment
            milstein_increment = rfr * current_value * dt + vol * current_value * increments[j] + 0.5 * vol**2 * current_value * (increments[j] ** 2 - dt)  
            current_value += milstein_increment                # Update the current value
            milstein_path.append(current_value)                # Add the current value to the path
        
        MS_paths.append(milstein_path)

    return MS_paths

Set initial Parameters¶

In [3]:
# Inital assignment of parameters based on values provided in the exam

S0 = 100      # Today's stock price
T = 1         # Time (time to expiry)
sigma = 0.2   # Volatility
r = 0.05      # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100       # Strike

# We define the number of time steps based on number of working days in a year (as per Tutorial)
t_steps = 252

Plot first simulated paths¶

In [4]:
# Run 1 simulation for each method
sims = 1

simulations_closed_form = closed_form(S0, r, sigma, T, t_steps, sims)
simulations_euler_maruyama = euler_maruyama(S0, r, sigma, T, t_steps, sims)
simulations_milstein = milstein_paths(S0, r, sigma, T, t_steps, sims)

# Plot the simulated stock paths from the three pre-defind functions"""

fig = go.Figure()

# Closed-Form GBM paths
for i, sim in enumerate(simulations_closed_form):
    fig.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Closed-Form {i+1}'))

# Euler-Maruyama GBM paths
for i, sim in enumerate(simulations_euler_maruyama):
    fig.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Euler-Maruyama {i+1}'))

# Milstein GBM paths
for i, sim in enumerate(simulations_milstein):
    fig.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Milstein {i+1}'))

fig.update_layout(xaxis_title='Time steps', yaxis_title='Stock Price')

fig.show()
In [5]:
sims = 20

simulations_closed_form = closed_form(S0, r, sigma, T, t_steps, sims)
simulations_euler_maruyama = euler_maruyama(S0, r, sigma, T, t_steps, sims)
simulations_milstein = milstein_paths(S0, r, sigma, T, t_steps, sims)
In [6]:
#Plot the simulated stock paths from the three pre-defined functions in separate graphs

# Closed-Form GBM paths
fig1 = go.Figure()
for i, sim in enumerate(simulations_closed_form):
    fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Closed-Form {i+1}'))
fig1.update_layout(title='Closed-Form GBM Paths', xaxis_title='Time steps', yaxis_title='Stock Price')
fig1.update_layout(showlegend=False)
fig1.show()

# Euler-Maruyama GBM paths
fig2 = go.Figure()
for i, sim in enumerate(simulations_euler_maruyama):
    fig2.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Euler-Maruyama {i+1}'))
fig2.update_layout(title='Euler-Maruyama GBM Paths', xaxis_title='Time steps', yaxis_title='Stock Price')
fig2.update_layout(showlegend=False)
fig2.show()

# Milstein GBM paths
fig3 = go.Figure()
for i, sim in enumerate(simulations_milstein):
    fig3.add_trace(go.Scatter(x=list(range(t_steps)), y=sim, mode='lines', name=f'Milstein {i+1}'))
fig3.update_layout(title='Milstein GBM Paths', xaxis_title='Time steps', yaxis_title='Stock Price')
fig3.update_layout(showlegend=False)
fig3.show()

Statistical validation ¶

Distribution of returns ¶

In [7]:
def plot_hist_qq(simulations, method_name):
    
    """This is a function provides a log return histogram and the Q-Q plot for the simulated paths provided in input"""
    
    # Calculate log returns
    log_returns = np.log(np.array(simulations)[:, 1:] / np.array(simulations)[:, :-1]).flatten()

    # Histogram
    plt.figure(figsize=(12, 4))
    plt.subplot(1, 2, 1)
    plt.hist(log_returns, bins=50, density=True, alpha=0.75)
    plt.title(f'{method_name} Log Returns Histogram')

    # Q-Q plot
    plt.subplot(1, 2, 2)
    stats.probplot(log_returns, dist="norm", plot=plt)
    plt.title(f'{method_name} Log Returns Q-Q Plot')

    plt.show()
    
In [8]:
# Plot histogram and Q-Q plot for each method
plot_hist_qq(simulations_closed_form, "Closed-Form")
plot_hist_qq(simulations_euler_maruyama, "Euler-Maruyama")
plot_hist_qq(simulations_milstein, "Milstein")

Autocorrelation test ¶

In [9]:
def Ljung_Box_autocorrelation(simulations, method_name):
    """This is a function is used to calculae the Ljung Box autocorrelation test"""
    
    # Calculate log returns
    log_returns = np.log(np.array(simulations)[:, 1:] / np.array(simulations)[:, :-1]).flatten()

    # ACF and PACF plots
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
    plot_acf(log_returns, ax=ax1, title=f'{method_name} Log Returns ACF')
    plot_pacf(log_returns, ax=ax2, title=f'{method_name} Log Returns PACF')

    plt.show()

    # Ljung-Box test
    lb_test = acorr_ljungbox(log_returns, lags=[10], return_df=True)
    return pd.DataFrame(data={'Method': [method_name],
                             'Ljung-Box Test Statistic': [lb_test['lb_stat'][10]],
                             'p-value': [lb_test['lb_pvalue'][10]]})
In [10]:
# Perform Ljung-Box test for each method and concatenate results into a single DataFrame
results_df = pd.concat([Ljung_Box_autocorrelation(simulations_closed_form, "Closed-Form"),
                        Ljung_Box_autocorrelation(simulations_euler_maruyama, "Euler-Maruyama"),
                        Ljung_Box_autocorrelation(simulations_milstein, "Milstein")], ignore_index=True)
In [11]:
print("Ljung-Box Test Results:")
print(results_df)
Ljung-Box Test Results:
           Method  Ljung-Box Test Statistic   p-value
0     Closed-Form                  5.078249  0.885890
1  Euler-Maruyama                  5.089118  0.885146
2        Milstein                  5.078317  0.885885

Sampling and averaging ¶

In [12]:
def continuous_arithmetic_avrg(paths_df):
    """
    Calculates the continuous arithmetic average for each stock price path in the 'paths_df' input DataFrame
    """
    cont_arith_df = paths_df.cumsum(axis=0) / np.arange(1, paths_df.shape[0] + 1).reshape(-1, 1)
    return cont_arith_df

def continuous_geometric_avrg(paths_df):
    """
    Calculates the continuous geometric average for each stock price path in the 'paths_df' input DataFrame
    """
    cont_geom_df = np.exp(np.log(paths_df).cumsum(axis=0) / np.arange(1, paths_df.shape[0] + 1).reshape(-1, 1))
    return cont_geom_df
In [13]:
def discrete_arithmetic_avrg(paths_df, periods):
    """
    Calculates the discrete arithmetic average for each stock price path in the 'paths_df' input DataFrame
    """
    # Get the number of simulations (columns) and time steps (rows)
    n_sims = paths_df.shape[1]
    t_steps = paths_df.shape[0]

    # Calculate the number of periods
    num_periods = t_steps // periods
    if t_steps % periods != 0:
        # If there is a remainder, add an additional period
        num_periods += 1

    # Initialize an output DataFrame with zeros and the same columns as paths_df
    ari_avgs = pd.DataFrame(0, index=np.arange(1), columns=paths_df.columns)

    # Loop through each stock price path (simulation)
    for i in range(n_sims):
        # Initialize a list to store stock prices at the beginning of each period for the current simulation
        period_vals = []
        
        # Loop through each period
        for j in range(num_periods):
            # Calculate the start and end indices of the current period
            start_idx = j * periods
            end_idx = min(start_idx + periods, t_steps)
            period_length = end_idx - start_idx

            # If the current period is not empty, add the stock price at the beginning of the period to the list
            if period_length > 0:
                period_val = paths_df.iloc[start_idx, i]
                period_vals.append(period_val)

        # Calculate the arithmetic average of the stock prices at the beginning of each period for the current simulation
        # and store the result in the output DataFrame (ari_avgs)
        ari_avgs.iloc[0, i] = np.mean(period_vals)

    return ari_avgs

def discrete_geometric_avrg(paths_df, periods):
    """
    Calculates the discrete geometric average for each stock price path in the 'paths_df' input DataFrame
    """
    # Get the number of simulations (columns) and time steps (rows)
    n_sims = paths_df.shape[1]
    t_steps = paths_df.shape[0]

    # Calculate the number of periods
    num_periods = t_steps // periods
    if t_steps % periods != 0:
        # If there is a remainder, add an additional period
        num_periods += 1

    # Initialize an output DataFrame with zeros and the same columns as paths_df
    geom_avgs = pd.DataFrame(0, index=np.arange(1), columns=paths_df.columns)

    # Loop through each stock price path (simulation)
    for i in range(n_sims):
        # Initialize a list to store stock prices at the beginning of each period for the current simulation
        period_vals = []
        
        # Loop through each period
        for j in range(num_periods):
            # Calculate the start and end indices of the current period
            start_idx = j * periods
            end_idx = min(start_idx + periods, t_steps)
            period_length = end_idx - start_idx

            # If the current period is not empty, add the stock price at the beginning of the period to the list
            if period_length > 0:
                period_val = paths_df.iloc[start_idx, i]
                period_vals.append(period_val)

        # Calculate the geometric average of the stock prices at the beginning of each period for the current simulation
        # Take the natural logarithm of the stock prices, then calculate the arithmetic mean of the logarithms. 
        # Exponentiate the result to obtain the geometric average
        geom_avgs.iloc[0, i] = np.exp(np.mean(np.log(period_vals)))

    return geom_avgs

Averages Plots¶

In [14]:
# Set parameters for plots
periods = 21
n_sims = 1

# Run simulations
CF_path = closed_form(S0, r, sigma, T, t_steps, sims)

# Convert CF_path list to a DataFrame (used later in the discrete plot)
CF_path_df = pd.DataFrame(CF_path)

# Convert the lists of paths into DataFrames
CF_path_df = pd.DataFrame(CF_path).transpose()

# Calculate averages using the modified functions
CA_CF_df = continuous_arithmetic_avrg(CF_path_df)
DA_CF_df = discrete_arithmetic_avrg(CF_path_df, periods)
CG_CF_df = continuous_geometric_avrg(CF_path_df)
DG_CF_df = discrete_geometric_avrg(CF_path_df, periods)
In [15]:
# Calculate the continuous average value for the last timestep (252)
CA_CF_last = CA_CF_df.iloc[-1][0]
CG_CF_last = CG_CF_df.iloc[-1][0]

# Calculate the discrete average value by taking the mean of all the averages
DA_CF_mean = DA_CF_df.mean()[0]
DG_CF_mean = DG_CF_df.mean()[0]
In [16]:
def discrete_stock_path(stock_path_df, periods):
    """
    Generates a discrete stock path based on the 'stock_path_df' input DataFrame and the given period
    """
    t_steps = stock_path_df.shape[0]
    num_periods = t_steps // periods
    if t_steps % periods != 0:
        num_periods += 1

    discrete_path = []
    for j in range(num_periods):
        start_idx = j * periods
        end_idx = min(start_idx + periods, t_steps)
        period_value = stock_path_df.iloc[start_idx]
        discrete_path.extend([period_value] * (end_idx - start_idx))

    return discrete_path
In [17]:
fig1 = go.Figure() # Continuous Arithmetic Averages Plot
fig2 = go.Figure() # Discrete Arithmetic Averages Plot

#Stock price
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=CF_path[0], mode='lines', name='Exact_price'))
fig2.add_trace(go.Scatter(x=list(range(t_steps)), y=CF_path[0], mode='lines', name='Exact_price'))

# Continuous plot
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=CA_CF_df[0], mode='lines', name='CA'))
fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=CG_CF_df[0], mode='lines', name='CG'))

# Discrete plot
discrete_path = discrete_stock_path(CF_path_df[0], periods)
fig2.add_trace(go.Scatter(x=list(range(t_steps)), y=discrete_path, mode='lines',
                          name='Discrete_path', line=dict(color='purple')))

# Add horizontal dashed lines for the continuous mean
fig1.add_shape(type='line', x0=0, x1=t_steps-1, y0=CA_CF_last, y1=CA_CF_last,
               yref='y', xref='x', line=dict(color='red', dash='dash'), opacity = 0.3)
fig1.add_shape(type='line', x0=0, x1=t_steps-1, y0=CG_CF_last, y1=CG_CF_last,
               yref='y', xref='x', line=dict(color='green', dash='dash'), opacity = 0.3)

# Add horizontal dashed lines for the discrete mean
fig2.add_shape(type='line', x0=0, x1=t_steps-1, y0=DA_CF_mean, y1=DA_CF_mean,
               yref='y', xref='x', line=dict(color='red', dash='dash'), opacity = 0.3)
fig2.add_shape(type='line', x0=0, x1=t_steps-1, y0=DG_CF_mean, y1=DG_CF_mean,
               yref='y', xref='x', line=dict(color='green', dash='dash'), opacity = 0.3)

# Add legend entries for the dashed lines
fig1.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='red', dash='dash'),
                          name='Continuous Arithmetic Mean'))
fig1.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='green', dash='dash'),
                          name='Continuous Geometric Mean'))
fig2.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='red', dash='dash'), 
                          name='Discrete Arithmetic Mean'))
fig2.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='green', dash='dash'),
                          name='Discrete Geometric Mean'))

               
fig1.update_layout(title='Continuous Averages', xaxis_title='Time steps', yaxis_title='Stock Price')
fig2.update_layout(title='Discrete Averages', xaxis_title='Time steps', yaxis_title='Stock Price')         
               
fig1.show()
fig2.show()

Asian Option Payoff ¶

Fixed Strike Price ¶

In [18]:
def asian_put_payoff(average_prices, strike_price):
    """Calculates the payoff of an Asian put option with a fixed strike price."""
    
    payoffs = np.maximum(0, strike_price - average_prices)
    return payoffs

def asian_call_payoff(average_prices, strike_price):
    """ Calculates the payoff of an Asian call option with a fixed strike price."""
    payoffs = np.maximum(0, average_prices - strike_price)
    return payoffs
In [19]:
def asian_option_payoffs(paths_df, strike_price, chosen_frequency):
    """
    Calculates the payoffs for Asian call and put options using continuous and discrete arithmetic
    and geometric averages and consider computation time.
    """
    
    periods = freq_periods[chosen_frequency]

    # Calculate averages and measure time
    start_time_CA = time.time()
    CA_df = continuous_arithmetic_avrg(paths_df)
    elapsed_time_CA = time.time() - start_time_CA

    start_time_CG = time.time()
    CG_df = continuous_geometric_avrg(paths_df)
    elapsed_time_CG = time.time() - start_time_CG

    start_time_DA = time.time()
    DA_df = discrete_arithmetic_avrg(paths_df, periods)
    elapsed_time_DA = time.time() - start_time_DA

    start_time_DG = time.time()
    DG_df = discrete_geometric_avrg(paths_df, periods)
    elapsed_time_DG = time.time() - start_time_DG

    # Calculate payoffs for call and put options
    call_payoffs_CA = asian_call_payoff(CA_df.iloc[-1], strike_price)
    call_payoffs_CG = asian_call_payoff(CG_df.iloc[-1], strike_price)
    call_payoffs_DA = asian_call_payoff(DA_df.mean(), strike_price)
    call_payoffs_DG = asian_call_payoff(DG_df.mean(), strike_price)

    put_payoffs_CA = asian_put_payoff(CA_df.iloc[-1], strike_price)
    put_payoffs_CG = asian_put_payoff(CG_df.iloc[-1], strike_price)
    put_payoffs_DA = asian_put_payoff(DA_df.mean(), strike_price)
    put_payoffs_DG = asian_put_payoff(DG_df.mean(), strike_price)

    # Calculate mean payoffs (based on discounted detailed in the exam)
    discount_factor = np.exp(-r * T)
    payoffs = {
        "Call_CA": discount_factor * call_payoffs_CA.mean(),
        "Call_CG": discount_factor * call_payoffs_CG.mean(),
        "Call_DA": discount_factor * call_payoffs_DA.mean(),
        "Call_DG": discount_factor * call_payoffs_DG.mean(),
        "Put_CA": discount_factor * put_payoffs_CA.mean(),
        "Put_CG": discount_factor * put_payoffs_CG.mean(),
        "Put_DA": discount_factor * put_payoffs_DA.mean(),
        "Put_DG": discount_factor * put_payoffs_DG.mean(),
    }

    # To calculate the computation time
    avg_times = {
        "CA_time": elapsed_time_CA,
        "CG_time": elapsed_time_CG,
        "DA_time": elapsed_time_DA,
        "DG_time": elapsed_time_DG
    }

    return payoffs, avg_times
In [20]:
# Define the frequency of periods
freq_periods = {
    'daily': 1,
    'weekly': 5,
    'biweekly': 10,
    'monthly': 21,
    'quarterly': 63,
    'semester': 126
}

# Set parameters
S0 = 100
r = 0.05
sigma = 0.2
T = 1
t_steps = 252
sims = 3000
E = 100
In [21]:
# Generate paths and measure time
start_time_CF_paths = time.time()
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
time_CF_paths = time.time() - start_time_CF_paths

start_time_EM_paths = time.time()
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
time_EM_paths = time.time() - start_time_EM_paths

start_time_MS_paths = time.time()
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
time_MS_paths = time.time() - start_time_MS_paths
In [22]:
def measure_time(method, *args):
    start_time = time.time()
    result, avg_times = method(*args)
    end_time = time.time()
    elapsed_time = end_time - start_time
    return result, avg_times, elapsed_time
In [23]:
# Convert the lists of paths into DataFrames
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths_df = pd.DataFrame(MS_paths).transpose()
In [24]:
# Calculate payoffs and measure time
CF_payoffs, CF_avg_times, CF_payoffs_time = measure_time(asian_option_payoffs,
                                                         CF_paths_df, E, 'monthly')
EM_payoffs, EM_avg_times, EM_payoffs_time = measure_time(asian_option_payoffs,
                                                         EM_paths_df, E, 'monthly')
MS_payoffs, MS_avg_times, MS_payoffs_time = measure_time(asian_option_payoffs,
                                                         MS_paths_df, E, 'monthly')
In [25]:
# Combine the payoffs into a DataFrame
payoffs_df = pd.DataFrame({'Closed Form': CF_payoffs,
                           'Euler-Maruyama': EM_payoffs,
                           'Milstein': MS_payoffs}).transpose()

# Display the table
payoffs_df
Out[25]:
Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
Closed Form 5.972005 5.742982 5.894104 5.646514 3.291008 3.409279 3.208988 3.333806
Euler-Maruyama 5.971964 5.743228 5.894126 5.646688 3.287663 3.406148 3.205987 3.330861
Milstein 5.970964 5.742027 5.893078 5.645582 3.290267 3.408488 3.208263 3.333029
In [26]:
# Create dictionary for each scheme
path_times = {"Closed Form": time_CF_paths,"Euler-Maruyama": time_EM_paths,"Milstein Scheme": time_MS_paths}

CA_times = {"Closed Form": CF_avg_times["CA_time"],"Euler-Maruyama": EM_avg_times["CA_time"],
            "Milstein Scheme": MS_avg_times["CA_time"]}
CG_times = {"Closed Form": CF_avg_times["CG_time"],"Euler-Maruyama": EM_avg_times["CG_time"],
            "Milstein Scheme": MS_avg_times["CG_time"]}
DA_times = {"Closed Form": CF_avg_times["DA_time"],"Euler-Maruyama": EM_avg_times["DA_time"],
            "Milstein Scheme": MS_avg_times["DA_time"]}
DG_times = {"Closed Form": CF_avg_times["DG_time"],"Euler-Maruyama": EM_avg_times["DG_time"],
            "Milstein Scheme": MS_avg_times["DG_time"]}

# Create DataFrame from dictionaries
running_times_df = pd.DataFrame({
    "Path Generation": path_times,
    "Payoff CA": CA_times,
    "Payoff CG": CG_times,
    "Payoff DA": DA_times,
    "Payoff DG": DG_times
})
# Display computation time
running_times_df
Out[26]:
Path Generation Payoff CA Payoff CG Payoff DA Payoff DG
Closed Form 0.059918 0.015118 0.022587 1.100632 1.139448
Euler-Maruyama 0.329699 0.017061 0.018135 1.186121 1.292552
Milstein Scheme 0.795896 0.013098 0.019768 0.938483 0.899511

Floating Strike Price ¶

In [27]:
def floating_option_payoffs(paths_df, chosen_frequency):
    """
    Calculates the payoffs for Asian call and put options with a floating strike price,
    using continuous and discrete arithmetic and geometric averages, and consider computation time.
    """

    periods = freq_periods[chosen_frequency]

    # Calculate averages and measure time
    start_time_CA = time.time()
    CA_df = continuous_arithmetic_avrg(paths_df)
    elapsed_time_CA = time.time() - start_time_CA

    start_time_CG = time.time()
    CG_df = continuous_geometric_avrg(paths_df)
    elapsed_time_CG = time.time() - start_time_CG

    start_time_DA = time.time()
    DA_df = discrete_arithmetic_avrg(paths_df, periods)
    elapsed_time_DA = time.time() - start_time_DA

    start_time_DG = time.time()
    DG_df = discrete_geometric_avrg(paths_df, periods)
    elapsed_time_DG = time.time() - start_time_DG

    # Calculate payoffs for call and put options
    final_asset_price = paths_df.iloc[-1]
    call_payoffs_CA = asian_call_payoff(final_asset_price, CA_df.iloc[-1])
    call_payoffs_CG = asian_call_payoff(final_asset_price, CG_df.iloc[-1])
    call_payoffs_DA = asian_call_payoff(final_asset_price, DA_df.mean())
    call_payoffs_DG = asian_call_payoff(final_asset_price, DG_df.mean())

    put_payoffs_CA = asian_put_payoff(final_asset_price, CA_df.iloc[-1])
    put_payoffs_CG = asian_put_payoff(final_asset_price, CG_df.iloc[-1])
    put_payoffs_DA = asian_put_payoff(final_asset_price, DA_df.mean())
    put_payoffs_DG = asian_put_payoff(final_asset_price, DG_df.mean())

    # Calculate mean payoffs (based on discounted detailed in the exam)
    discount_factor = np.exp(-r * T)
    payoffs = {
        "Call_CA": discount_factor * call_payoffs_CA.mean(),
        "Call_CG": discount_factor * call_payoffs_CG.mean(),
        "Call_DA": discount_factor * call_payoffs_DA.mean(),
        "Call_DG": discount_factor * call_payoffs_DG.mean(),
        "Put_CA": discount_factor * put_payoffs_CA.mean(),
        "Put_CG": discount_factor * put_payoffs_CG.mean(),
        "Put_DA": discount_factor * put_payoffs_DA.mean(),
        "Put_DG": discount_factor * put_payoffs_DG.mean(),
    }

    # To calculate the computation time
    avg_times = {
        "CA_time": elapsed_time_CA,
        "CG_time": elapsed_time_CG,
        "DA_time": elapsed_time_DA,
        "DG_time": elapsed_time_DG
    }

    return payoffs, avg_times
In [28]:
# Calculate payoffs and measure time
CF_payoffs, CF_avg_times, CF_payoffs_time = measure_time(floating_option_payoffs,
                                                         CF_paths_df, 'monthly')
EM_payoffs, EM_avg_times, EM_payoffs_time = measure_time(floating_option_payoffs,
                                                         EM_paths_df, 'monthly')
MS_payoffs, MS_avg_times, MS_payoffs_time = measure_time(floating_option_payoffs,
                                                         MS_paths_df, 'monthly')
In [29]:
# Combine the payoffs into a DataFrame
payoffs_df = pd.DataFrame({'Closed Form': CF_payoffs,
                           'Euler-Maruyama': EM_payoffs,
                           'Milstein': MS_payoffs}).transpose()

# Display the table
payoffs_df
Out[29]:
Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
Closed Form 5.986836 6.206926 5.904629 6.142226 3.254626 3.127422 3.176537 3.041727
Euler-Maruyama 5.985910 6.205819 5.903487 6.140985 3.255591 3.128279 3.177007 3.042192
Milstein 5.985773 6.205782 5.903583 6.141095 3.253873 3.126724 3.175801 3.041051
In [30]:
# Create dictionary for each scheme
path_times = {"Closed Form": time_CF_paths,"Euler-Maruyama": time_EM_paths,"Milstein Scheme": time_MS_paths}

CA_times = {"Closed Form": CF_avg_times["CA_time"],"Euler-Maruyama": EM_avg_times["CA_time"],
            "Milstein Scheme": MS_avg_times["CA_time"]}
CG_times = {"Closed Form": CF_avg_times["CG_time"],"Euler-Maruyama": EM_avg_times["CG_time"],
            "Milstein Scheme": MS_avg_times["CG_time"]}
DA_times = {"Closed Form": CF_avg_times["DA_time"],"Euler-Maruyama": EM_avg_times["DA_time"],
            "Milstein Scheme": MS_avg_times["DA_time"]}
DG_times = {"Closed Form": CF_avg_times["DG_time"],"Euler-Maruyama": EM_avg_times["DG_time"],
            "Milstein Scheme": MS_avg_times["DG_time"]}

# Create DataFrame from dictionaries
running_times_df = pd.DataFrame({
    "Path Generation": path_times,
    "Payoff CA": CA_times,
    "Payoff CG": CG_times,
    "Payoff DA": DA_times,
    "Payoff DG": DG_times
})
# Display computation time
running_times_df
Out[30]:
Path Generation Payoff CA Payoff CG Payoff DA Payoff DG
Closed Form 0.059918 0.018096 0.020042 0.900321 0.928178
Euler-Maruyama 0.329699 0.010199 0.015438 0.963956 1.008374
Milstein Scheme 0.795896 0.016299 0.016369 0.963018 0.947251

Impact of volatility on Asian options ¶

Fixed strike¶

In [31]:
#Firstly, we update the function to simplify the methodology here. We remove the use of computation time,
# but keep the remaining of the payoff function as is.

def asian_option_payoffs_param(paths_df, strike_price, chosen_frequency):
    """
    Calculates the payoffs for Asian call and put options using continuous and discrete arithmetic
    and geometric averages, but do not consider computation time.
    """
    periods = freq_periods[chosen_frequency]

    # Calculate averages
    CA_df = continuous_arithmetic_avrg(paths_df)
    CG_df = continuous_geometric_avrg(paths_df)
    DA_df = discrete_arithmetic_avrg(paths_df, periods)
    DG_df = discrete_geometric_avrg(paths_df, periods)

    # Calculate payoffs for call and put options
    call_payoffs_CA = asian_call_payoff(CA_df.iloc[-1], strike_price)
    call_payoffs_CG = asian_call_payoff(CG_df.iloc[-1], strike_price)
    call_payoffs_DA = asian_call_payoff(DA_df.mean(), strike_price)
    call_payoffs_DG = asian_call_payoff(DG_df.mean(), strike_price)

    put_payoffs_CA = asian_put_payoff(CA_df.iloc[-1], strike_price)
    put_payoffs_CG = asian_put_payoff(CG_df.iloc[-1], strike_price)
    put_payoffs_DA = asian_put_payoff(DA_df.mean(), strike_price)
    put_payoffs_DG = asian_put_payoff(DG_df.mean(), strike_price)

    # Calculate mean payoffs (based on discounted detailed in the exam)
    discount_factor = np.exp(-r * T)
    payoffs = {
        "Call_CA": discount_factor * call_payoffs_CA.mean(),
        "Call_CG": discount_factor * call_payoffs_CG.mean(),
        "Call_DA": discount_factor * call_payoffs_DA.mean(),
        "Call_DG": discount_factor * call_payoffs_DG.mean(),
        "Put_CA": discount_factor * put_payoffs_CA.mean(),
        "Put_CG": discount_factor * put_payoffs_CG.mean(),
        "Put_DA": discount_factor * put_payoffs_DA.mean(),
        "Put_DG": discount_factor * put_payoffs_DG.mean(),
    }

    return payoffs
In [32]:
# Inital assignment of parameters based on values provided in the exam. We will be testing the sigma parameter

S0 = 100      # Today's stock price
T = 1         # Time (time to expiry)
#sigma = 0.2   # Volatility
r = 0.05      # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100       # Strike
sims = 3000
In [33]:
volatilities = np.arange(0.10, 0.80, 0.10)
results = []

for vol in volatilities:
    # Generate paths with the current volatility
    CF_paths = closed_form(S0, r, vol, T, t_steps, sims)
    CF_paths_df = pd.DataFrame(CF_paths).transpose()

    EM_paths = euler_maruyama(S0, r, vol, T, t_steps, sims)
    EM_paths_df = pd.DataFrame(EM_paths).transpose()

    MS_paths = milstein_paths(S0, r, vol, T, t_steps, sims)
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs for the current volatility
    CF_payoffs = asian_option_payoffs_param(CF_paths_df, E, 'monthly')
    EM_payoffs = asian_option_payoffs_param(EM_paths_df, E, 'monthly')
    MS_payoffs = asian_option_payoffs_param(MS_paths_df, E, 'monthly')

    # Append the results to the list
    results.append({"Volatility": vol, "Scheme": "Closed Form", **CF_payoffs})
    results.append({"Volatility": vol, "Scheme": "Euler-Maruyama", **EM_payoffs})
    results.append({"Volatility": vol, "Scheme": "Milstein", **MS_payoffs})

# Create the results DataFrame
volatility_results_df = pd.DataFrame(results)

# Display the DataFrame
print("Impact of changing volatility:")
display(volatility_results_df)
Impact of changing volatility:
Volatility Scheme Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
0 0.1 Closed Form 3.743725 3.670867 3.710083 3.630641 1.201805 1.224048 1.164966 1.188215
1 0.1 Euler-Maruyama 3.743685 3.670882 3.709973 3.630545 1.201107 1.223374 1.164267 1.187497
2 0.1 Milstein 3.743138 3.670304 3.709503 3.630089 1.201488 1.223719 1.164656 1.187896
3 0.2 Closed Form 5.972005 5.742982 5.894104 5.646514 3.291008 3.409279 3.208988 3.333806
4 0.2 Euler-Maruyama 5.971964 5.743228 5.894126 5.646688 3.287663 3.406148 3.205987 3.330861
5 0.2 Milstein 5.970964 5.742027 5.893078 5.645582 3.290267 3.408488 3.208263 3.333029
6 0.3 Closed Form 8.269677 7.789369 8.150250 7.636584 5.434399 5.720766 5.310407 5.617529
7 0.3 Euler-Maruyama 8.271807 7.791813 8.151936 7.638587 5.429016 5.715694 5.305216 5.612588
8 0.3 Milstein 8.268207 7.788084 8.148811 7.635337 5.433255 5.719508 5.309294 5.616289
9 0.4 Closed Form 10.584858 9.759435 10.431338 9.545610 7.579880 8.107131 7.421597 7.982982
10 0.4 Euler-Maruyama 10.589223 9.764751 10.435362 9.551374 7.571156 8.099747 7.413685 7.977058
11 0.4 Milstein 10.583005 9.757905 10.429525 9.544119 7.578364 8.105416 7.420122 7.981272
12 0.5 Closed Form 12.909127 11.637518 12.722348 11.358370 9.718288 10.551037 9.526534 10.412777
13 0.5 Euler-Maruyama 12.918743 11.650529 12.730578 11.368900 9.708104 10.545624 9.516812 10.406463
14 0.5 Milstein 12.906946 11.635813 12.720186 11.356744 9.716430 10.548853 9.524694 10.410620
15 0.6 Closed Form 15.238528 13.422634 15.016804 13.069634 11.844343 13.048933 11.617089 12.898512
16 0.6 Euler-Maruyama 15.257158 13.441674 15.032521 13.084861 11.835592 13.043875 11.608177 12.891925
17 0.6 Milstein 15.236125 13.420812 15.014401 13.067997 11.842208 13.046247 11.614950 12.895941
18 0.7 Closed Form 17.577872 15.110497 17.319527 14.674069 13.960886 15.593086 13.695711 15.430695
19 0.7 Euler-Maruyama 17.604554 15.135966 17.340344 14.696822 13.952032 15.589495 13.684911 15.427528
20 0.7 Milstein 17.575221 15.108758 17.317021 14.672498 13.958394 15.590000 13.693352 15.427686

Floating strike¶

In [34]:
def asian_option_param_float(paths_df, chosen_frequency):
    """
    Calculates the payoffs for Asian call and put options with a floating strike price,
    using continuous and discrete arithmetic and geometric averages.
    """

    periods = freq_periods[chosen_frequency]

    # Calculate averages
    CA_df = continuous_arithmetic_avrg(paths_df)
    CG_df = continuous_geometric_avrg(paths_df)
    DA_df = discrete_arithmetic_avrg(paths_df, periods)
    DG_df = discrete_geometric_avrg(paths_df, periods)

    # Calculate payoffs for call and put options
    final_asset_price = paths_df.iloc[-1]
    call_payoffs_CA = asian_call_payoff(final_asset_price, CA_df.iloc[-1])
    call_payoffs_CG = asian_call_payoff(final_asset_price, CG_df.iloc[-1])
    call_payoffs_DA = asian_call_payoff(final_asset_price, DA_df.mean())
    call_payoffs_DG = asian_call_payoff(final_asset_price, DG_df.mean())

    put_payoffs_CA = asian_put_payoff(final_asset_price, CA_df.iloc[-1])
    put_payoffs_CG = asian_put_payoff(final_asset_price, CG_df.iloc[-1])
    put_payoffs_DA = asian_put_payoff(final_asset_price, DA_df.mean())
    put_payoffs_DG = asian_put_payoff(final_asset_price, DG_df.mean())

    # Calculate mean payoffs (based on discounted detailed in the exam)
    discount_factor = np.exp(-r * T)
    payoffs = {
        "Call_CA": discount_factor * call_payoffs_CA.mean(),
        "Call_CG": discount_factor * call_payoffs_CG.mean(),
        "Call_DA": discount_factor * call_payoffs_DA.mean(),
        "Call_DG": discount_factor * call_payoffs_DG.mean(),
        "Put_CA": discount_factor * put_payoffs_CA.mean(),
        "Put_CG": discount_factor * put_payoffs_CG.mean(),
        "Put_DA": discount_factor * put_payoffs_DA.mean(),
        "Put_DG": discount_factor * put_payoffs_DG.mean(),
    }

    return payoffs
In [35]:
# Initial assignment of parameters based on values provided in the exam. We will be testing the sigma parameter
S0 = 100      # Today's stock price
T = 1         # Time (time to expiry)
# sigma = 0.2   # Volatility
r = 0.05      # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100       # Strike
sims = 3000
In [36]:
volatilities = np.arange(0.10, 0.80, 0.10)
results = []

for vol in volatilities:
    # Generate paths with the current volatility
    CF_paths = closed_form(S0, r, vol, T, t_steps, sims)
    CF_paths_df = pd.DataFrame(CF_paths).transpose()

    EM_paths = euler_maruyama(S0, r, vol, T, t_steps, sims)
    EM_paths_df = pd.DataFrame(EM_paths).transpose()

    MS_paths = milstein_paths(S0, r, vol, T, t_steps, sims)
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs for the current volatility
    CF_payoffs = asian_option_param_float(CF_paths_df, 'monthly')
    EM_payoffs = asian_option_param_float(EM_paths_df, 'monthly')
    MS_payoffs = asian_option_param_float(MS_paths_df, 'monthly')

    # Append the results to the list
    results.append({"Volatility": vol, "Scheme": "Closed Form", **CF_payoffs})
    results.append({"Volatility": vol, "Scheme": "Euler-Maruyama", **EM_payoffs})
    results.append({"Volatility": vol, "Scheme": "Milstein", **MS_payoffs})

# Create the results DataFrame
volatility_results_df = pd.DataFrame(results)

# Display the DataFrame
print("Impact of changing volatility:")
display(volatility_results_df)
Impact of changing volatility:
Volatility Scheme Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
0 0.1 Closed Form 3.761406 3.832980 3.723484 3.801625 1.167881 1.144355 1.133157 1.108607
1 0.1 Euler-Maruyama 3.760804 3.832342 3.722992 3.801072 1.167933 1.144400 1.133248 1.108671
2 0.1 Milstein 3.760810 3.832360 3.722897 3.801012 1.167566 1.144052 1.132850 1.108311
3 0.2 Closed Form 5.986836 6.206926 5.904629 6.142226 3.254626 3.127422 3.176537 3.041727
4 0.2 Euler-Maruyama 5.985910 6.205819 5.903487 6.140985 3.255591 3.128279 3.177007 3.042192
5 0.2 Milstein 5.985773 6.205782 5.903583 6.141095 3.253873 3.126724 3.175801 3.041051
6 0.3 Closed Form 8.305807 8.754126 8.180423 8.663608 5.428141 5.109785 5.307323 4.969720
7 0.3 Euler-Maruyama 8.304330 8.752106 8.179810 8.662626 5.430747 5.111851 5.310156 4.972250
8 0.3 Milstein 8.304297 8.752447 8.178932 8.661938 5.426964 5.108739 5.306165 4.968702
9 0.4 Closed Form 10.661334 11.416945 10.495073 11.308435 7.627208 7.030145 7.465709 6.831958
10 0.4 Euler-Maruyama 10.661667 11.416182 10.495472 11.307382 7.634897 7.036349 7.472311 6.836860
11 0.4 Milstein 10.659414 11.414776 10.493193 11.306249 7.625618 7.028827 7.464158 6.830659
12 0.5 Closed Form 13.062710 14.192757 12.847939 14.069032 9.855196 8.880884 9.645400 8.616272
13 0.5 Euler-Maruyama 13.064943 14.192219 12.851228 14.070378 9.869353 8.890896 9.658767 8.626587
14 0.5 Milstein 13.060450 14.190131 12.845780 14.066413 9.853204 8.879328 9.643510 8.614774
15 0.6 Closed Form 15.505706 17.084350 15.238282 16.949522 12.099914 10.658074 11.838020 10.320667
16 0.6 Euler-Maruyama 15.504924 17.085536 15.241211 16.952354 12.117312 10.674158 11.856378 10.336112
17 0.6 Milstein 15.503165 17.081316 15.235861 16.946597 12.097479 10.656277 11.835709 10.319051
18 0.7 Closed Form 17.987082 20.091709 17.662007 19.947807 14.347684 12.352735 14.029439 11.934796
19 0.7 Euler-Maruyama 17.989984 20.093014 17.664215 19.949727 14.377408 12.374387 14.054550 11.953923
20 0.7 Milstein 17.984517 20.088370 17.659629 19.944639 14.344901 12.350686 14.026855 11.933009

Impact of return on Asian options ¶

Fixed strike¶

In [37]:
# Inital assignment of parameters based on values provided in the exam. We will be testing the sigma parameter

S0 = 100      # Today's stock price
T = 1         # Time (time to expiry)
sigma = 0.2   # Volatility
#r = 0.05      # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100       # Strike
sims = 3000
In [38]:
returns = np.arange(-0.1, 0.11, 0.05)
results = []

for r in returns:
    # Generate paths with the current volatility
    CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
    CF_paths_df = pd.DataFrame(CF_paths).transpose()

    EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
    EM_paths_df = pd.DataFrame(EM_paths).transpose()

    MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs for the current volatility
    CF_payoffs = asian_option_payoffs_param(CF_paths_df, E, 'monthly')
    EM_payoffs = asian_option_payoffs_param(EM_paths_df, E, 'monthly')
    MS_payoffs = asian_option_payoffs_param(MS_paths_df, E, 'monthly')

    # Append the results to the list
    results.append({"Return": r, "Scheme": "Closed Form", **CF_payoffs})
    results.append({"Return": r, "Scheme": "Euler-Maruyama", **EM_payoffs})
    results.append({"Return": r, "Scheme": "Milstein", **MS_payoffs})

# Create the results DataFrame
return_results_df = pd.DataFrame(results)

# Format the "Return" column
return_results_df["Return"] = return_results_df["Return"].apply(lambda x: round(x, 2))

# Display the DataFrame
print("Impact of changing returns:")
display(return_results_df)
Impact of changing returns:
Return Scheme Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
0 -0.10 Closed Form 2.864473 2.754214 2.775438 2.660648 7.935884 8.224332 7.838564 8.153396
1 -0.10 Euler-Maruyama 2.866918 2.756720 2.777419 2.662674 7.935385 8.224219 7.837909 8.153111
2 -0.10 Milstein 2.865978 2.755616 2.776901 2.662005 7.938291 8.226933 7.840927 8.155973
3 -0.05 Closed Form 3.754577 3.612983 3.665048 3.514788 6.068977 6.286738 5.976019 6.210615
4 -0.05 Euler-Maruyama 3.755881 3.613984 3.666710 3.516234 6.066707 6.284345 5.974406 6.208956
5 -0.05 Milstein 3.755442 3.613785 3.665895 3.515571 6.070031 6.287869 5.977055 6.211736
6 0.00 Closed Form 4.794004 4.613055 4.710666 4.516877 4.527474 4.689282 4.442010 4.614632
7 0.00 Euler-Maruyama 4.795076 4.614321 4.711844 4.518238 4.524860 4.686914 4.439791 4.612632
8 0.00 Milstein 4.794022 4.613072 4.710681 4.516892 4.527487 4.689296 4.442021 4.614643
9 0.05 Closed Form 5.972005 5.742982 5.894104 5.646514 3.291008 3.409279 3.208988 3.333806
10 0.05 Euler-Maruyama 5.971964 5.743228 5.894126 5.646688 3.287663 3.406148 3.205987 3.330861
11 0.05 Milstein 5.970964 5.742027 5.893078 5.645582 3.290267 3.408488 3.208263 3.333029
12 0.10 Closed Form 7.255757 6.970206 7.194305 6.881914 2.317638 2.403515 2.247018 2.335700
13 0.10 Euler-Maruyama 7.255656 6.970164 7.193898 6.881774 2.315086 2.400815 2.244434 2.333144
14 0.10 Milstein 7.253449 6.968102 7.192027 6.879858 2.316407 2.402206 2.245820 2.334422

Floating strike¶

In [39]:
returns = np.arange(-0.1, 0.11, 0.05)
results = []

for r in returns:
    # Generate paths with the current volatility
    CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
    CF_paths_df = pd.DataFrame(CF_paths).transpose()

    EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
    EM_paths_df = pd.DataFrame(EM_paths).transpose()

    MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs for the current volatility
    CF_payoffs = asian_option_param_float(CF_paths_df, 'monthly')
    EM_payoffs = asian_option_param_float(EM_paths_df, 'monthly')
    MS_payoffs = asian_option_param_float(MS_paths_df, 'monthly')

    # Append the results to the list
    results.append({"Return": r, "Scheme": "Closed Form", **CF_payoffs})
    results.append({"Return": r, "Scheme": "Euler-Maruyama", **EM_payoffs})
    results.append({"Return": r, "Scheme": "Milstein", **MS_payoffs})

# Create the results DataFrame
return_results_df = pd.DataFrame(results)

# Format the "Return" column
return_results_df["Return"] = return_results_df["Return"].apply(lambda x: round(x, 2))

# Display the DataFrame
print("Impact of changing return:")
display(return_results_df)
Impact of changing return:
Return Scheme Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
0 -0.10 Closed Form 2.749910 2.848652 2.671094 2.774829 7.659442 7.359476 7.588910 7.263022
1 -0.10 Euler-Maruyama 2.749790 2.848552 2.670972 2.774575 7.662004 7.361734 7.591164 7.264820
2 -0.10 Milstein 2.751335 2.850167 2.672483 2.776313 7.661727 7.361554 7.591162 7.265050
3 -0.05 Closed Form 3.651982 3.781907 3.568536 3.707359 5.928541 5.699112 5.848525 5.602492
4 -0.05 Euler-Maruyama 3.652306 3.782399 3.569059 3.707855 5.930798 5.701356 5.850681 5.604451
5 -0.05 Milstein 3.652820 3.782801 3.569358 3.708238 5.929572 5.700058 5.849539 5.603414
6 0.00 Closed Form 4.720829 4.891752 4.640412 4.823548 4.451210 4.279375 4.372918 4.189644
7 0.00 Euler-Maruyama 4.720763 4.891584 4.640476 4.823398 4.452804 4.280814 4.374353 4.190828
8 0.00 Milstein 4.720845 4.891767 4.640425 4.823561 4.451227 4.279390 4.372932 4.189657
9 0.05 Closed Form 5.986836 6.206926 5.904629 6.142226 3.254626 3.127422 3.176537 3.041727
10 0.05 Euler-Maruyama 5.985910 6.205819 5.903487 6.140985 3.255591 3.128279 3.177007 3.042192
11 0.05 Milstein 5.985773 6.205782 5.903583 6.141095 3.253873 3.126724 3.175801 3.041051
12 0.10 Closed Form 7.420827 7.698624 7.339389 7.643309 2.306538 2.212907 2.234268 2.137115
13 0.10 Euler-Maruyama 7.418813 7.696273 7.337676 7.641419 2.307173 2.213412 2.234931 2.137839
14 0.10 Milstein 7.418425 7.696026 7.337023 7.640734 2.305281 2.211734 2.233043 2.135983

Impact of Time Horizon on Asian options ¶

Fixed Strikes¶

In [40]:
# Set parameters
S0 = 100
r = 0.05
sigma = 0.2
#T = 1
#t_steps = 252
sims = 3000
E = 100
In [41]:
horizon = np.arange(0.5, 3.1, 0.5)
results = []

for T in horizon:
    # We defined in the lecture that we can consider continuity with 252 times steps (business day) per year. 
    # We force an integer value for random number selction
    t_steps = math.ceil(252*T) 
    
    # Generate paths with the current volatility
    CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
    CF_paths_df = pd.DataFrame(CF_paths).transpose()

    EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
    EM_paths_df = pd.DataFrame(EM_paths).transpose()

    MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs for the current volatility
    CF_payoffs = asian_option_payoffs_param(CF_paths_df, E, 'monthly')
    EM_payoffs = asian_option_payoffs_param(EM_paths_df, E, 'monthly')
    MS_payoffs = asian_option_payoffs_param(MS_paths_df, E, 'monthly')

    # Append the results to the list
    results.append({"Horizon": T, "Scheme": "Closed Form", **CF_payoffs})
    results.append({"Horizon": T, "Scheme": "Euler-Maruyama", **EM_payoffs})
    results.append({"Horizon": T, "Scheme": "Milstein", **MS_payoffs})

# Create the results DataFrame
T_results_df = pd.DataFrame(results)

# Display the DataFrame
print("Impact of changing time horizon:")
display(T_results_df)
Impact of changing time horizon:
Horizon Scheme Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
0 0.5 Closed Form 4.038655 3.931050 3.921397 3.798712 2.616748 2.683561 2.512477 2.587788
1 0.5 Euler-Maruyama 4.038364 3.931034 3.921259 3.798670 2.615816 2.682869 2.511815 2.587164
2 0.5 Milstein 4.037922 3.930358 3.920685 3.798047 2.616186 2.682972 2.511934 2.587215
3 1.0 Closed Form 5.972005 5.742982 5.894104 5.646514 3.291008 3.409279 3.208988 3.333806
4 1.0 Euler-Maruyama 5.971964 5.743228 5.894126 5.646688 3.287663 3.406148 3.205987 3.330861
5 1.0 Milstein 5.970964 5.742027 5.893078 5.645582 3.290267 3.408488 3.208263 3.333029
6 1.5 Closed Form 7.360125 7.007424 7.301167 6.928878 3.659060 3.820438 3.596832 3.763554
7 1.5 Euler-Maruyama 7.361107 7.008432 7.302391 6.930424 3.657128 3.818338 3.595252 3.762093
8 1.5 Milstein 7.358875 7.006304 7.299934 6.927779 3.658187 3.819495 3.595976 3.762623
9 2.0 Closed Form 8.679627 8.198243 8.628902 8.126528 4.055962 4.252549 3.999215 4.201207
10 2.0 Euler-Maruyama 8.682744 8.201341 8.631843 8.129543 4.054443 4.251182 3.997680 4.199904
11 2.0 Milstein 8.678171 8.196964 8.627458 8.125268 4.054970 4.251471 3.998235 4.200139
12 2.5 Closed Form 9.799946 9.185370 9.766331 9.130055 4.044069 4.280286 4.002612 4.243991
13 2.5 Euler-Maruyama 9.804486 9.189675 9.770726 9.134522 4.045762 4.281887 4.004179 4.245775
14 2.5 Milstein 9.798363 9.184008 9.764750 9.128706 4.043052 4.279165 4.001601 4.242874
15 3.0 Closed Form 10.960915 10.222550 10.930923 10.168882 4.394426 4.658441 4.351662 4.619696
16 3.0 Euler-Maruyama 10.959922 10.221459 10.930092 10.167777 4.390681 4.654629 4.348189 4.615971
17 3.0 Milstein 10.959161 10.221067 10.929178 10.167414 4.393310 4.657214 4.350557 4.618476

Floating strike¶

In [42]:
horizon = np.arange(0.5, 3.1, 0.5)
results = []

for T in horizon:
    # We defined in the lecture that we can consider continuity with 252 times steps (business day) per year
    t_steps = math.ceil(252*T)
    
    # Generate paths with the current volatility
    CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
    CF_paths_df = pd.DataFrame(CF_paths).transpose()

    EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
    EM_paths_df = pd.DataFrame(EM_paths).transpose()

    MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs for the current volatility
    CF_payoffs = asian_option_param_float(CF_paths_df, 'monthly')
    EM_payoffs = asian_option_param_float(EM_paths_df, 'monthly')
    MS_payoffs = asian_option_param_float(MS_paths_df, 'monthly')

    # Append the results to the list
    results.append({"Horizon": T, "Scheme": "Closed Form", **CF_payoffs})
    results.append({"Horizon": T, "Scheme": "Euler-Maruyama", **EM_payoffs})
    results.append({"Horizon": T, "Scheme": "Milstein", **MS_payoffs})

# Create the results DataFrame
T_results_df = pd.DataFrame(results)

# Display the DataFrame
print("Impact of changing return:")
display(T_results_df)
Impact of changing return:
Horizon Scheme Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
0 0.5 Closed Form 3.939561 4.044197 3.839106 3.959055 2.629035 2.559253 2.515593 2.437546
1 0.5 Euler-Maruyama 3.940138 4.044692 3.840010 3.959788 2.629671 2.559843 2.516439 2.438279
2 0.5 Milstein 3.938847 4.043444 3.838412 3.958317 2.628468 2.558714 2.515048 2.437034
3 1.0 Closed Form 5.986836 6.206926 5.904629 6.142226 3.254626 3.127422 3.176537 3.041727
4 1.0 Euler-Maruyama 5.985910 6.205819 5.903487 6.140985 3.255591 3.128279 3.177007 3.042192
5 1.0 Milstein 5.985773 6.205782 5.903583 6.141095 3.253873 3.126724 3.175801 3.041051
6 1.5 Closed Form 7.601809 7.943905 7.528586 7.889440 3.698014 3.526031 3.628062 3.449905
7 1.5 Euler-Maruyama 7.603452 7.945583 7.530389 7.890972 3.697779 3.526025 3.627878 3.449652
8 1.5 Milstein 7.600520 7.942490 7.527312 7.888036 3.697140 3.525230 3.627203 3.449123
9 2.0 Closed Form 9.142393 9.606169 9.079967 9.564216 4.043784 3.829589 3.987382 3.767265
10 2.0 Euler-Maruyama 9.143821 9.607642 9.081445 9.565576 4.044200 3.829878 3.987685 3.767293
11 2.0 Milstein 9.140846 9.604451 9.078432 9.562504 4.042790 3.828687 3.986398 3.766377
12 2.5 Closed Form 10.678234 11.258073 10.615689 11.218182 4.530128 4.259173 4.475424 4.200262
13 2.5 Euler-Maruyama 10.681513 11.261601 10.619217 11.221741 4.528682 4.257833 4.474208 4.198933
14 2.5 Milstein 10.676415 11.256048 10.613882 11.216164 4.528996 4.258162 4.474302 4.199266
15 3.0 Closed Form 11.759474 12.469183 11.704167 12.437640 4.482146 4.189474 4.439611 4.143008
16 3.0 Euler-Maruyama 11.761892 12.471052 11.706463 12.439490 4.483304 4.190052 4.440537 4.143467
17 3.0 Milstein 11.757494 12.466963 11.702204 12.435425 4.480962 4.188434 4.438442 4.141981

In-The-Money vs Out-of-The-Money options ¶

Fixed strike¶

In [43]:
#S0 = 100      # Today's stock price
T = 1         # Time (time to expiry)
sigma = 0.2   # Volatility
r = 0.05      # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100       # Strike
sims = 3000
In [44]:
start_prices = np.arange(85, 116, 5)
results = []

for S0 in start_prices:
    # Generate paths with the current volatility
    CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
    CF_paths_df = pd.DataFrame(CF_paths).transpose()

    EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
    EM_paths_df = pd.DataFrame(EM_paths).transpose()

    MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs for the current volatility
    CF_payoffs = asian_option_payoffs_param(CF_paths_df, E, 'monthly')
    EM_payoffs = asian_option_payoffs_param(EM_paths_df, E, 'monthly')
    MS_payoffs = asian_option_payoffs_param(MS_paths_df, E, 'monthly')

    # Append the results to the list
    results.append({"S0": S0, "Scheme": "Closed Form", **CF_payoffs})
    results.append({"S0": S0, "Scheme": "Euler-Maruyama", **EM_payoffs})
    results.append({"S0": S0, "Scheme": "Milstein", **MS_payoffs})

# Create the results DataFrame
ITM_OTM_results_df = pd.DataFrame(results)


# Display the DataFrame
print("Impact of changing starting price:")
display(ITM_OTM_results_df)
Impact of changing starting price:
S0 Scheme Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
0 85 Closed Form 0.584502 0.514149 0.575525 0.502857 12.909240 13.121523 12.895664 13.112951
1 85 Euler-Maruyama 0.584154 0.513706 0.575110 0.502313 12.907928 13.120134 12.894317 13.111493
2 85 Milstein 0.584363 0.514029 0.575388 0.502739 12.909160 13.121425 12.895585 13.112854
3 90 Closed Form 1.542434 1.418497 1.525848 1.398000 8.996689 9.172015 8.975234 9.154397
4 90 Euler-Maruyama 1.542117 1.418280 1.525616 1.397756 8.995351 9.170796 8.974015 9.153185
5 90 Milstein 1.542195 1.418279 1.525612 1.397783 8.996512 9.171819 8.975059 9.154202
6 95 Closed Form 3.186994 3.017563 3.165271 2.990116 5.770767 5.917223 5.743903 5.892816
7 95 Euler-Maruyama 3.186554 3.017302 3.164954 2.989921 5.769248 5.915905 5.742546 5.891600
8 95 Milstein 3.186688 3.017280 3.164966 2.989836 5.770526 5.916964 5.743664 5.892559
9 100 Closed Form 5.693648 5.479552 5.668779 5.448496 3.406939 3.525355 3.376658 3.497499
10 100 Euler-Maruyama 5.693276 5.479226 5.668292 5.448135 3.405432 3.523916 3.375075 3.496062
11 100 Milstein 5.693325 5.479255 5.668457 5.448201 3.406684 3.525083 3.376405 3.497228
12 105 Closed Form 8.993580 8.737693 8.973052 8.710542 1.836388 1.929640 1.810178 1.905848
13 105 Euler-Maruyama 8.994289 8.738548 8.973791 8.711428 1.835905 1.929326 1.809767 1.905605
14 105 Milstein 8.993279 8.737422 8.972752 8.710273 1.836158 1.929395 1.809950 1.905604
15 110 Closed Form 12.903636 12.606241 12.890038 12.584898 0.875961 0.944330 0.856411 0.926507
16 110 Euler-Maruyama 12.905194 12.607806 12.891531 12.586457 0.876270 0.944672 0.856699 0.926883
17 110 Milstein 12.903391 12.606027 12.889796 12.584689 0.875791 0.944144 0.856244 0.926325
18 115 Closed Form 17.275001 16.936247 17.269034 16.921975 0.376844 0.420480 0.364654 0.409887
19 115 Euler-Maruyama 17.276950 16.938200 17.271045 16.923872 0.377488 0.421153 0.365404 0.410547
20 115 Milstein 17.274819 16.936105 17.268855 16.921834 0.376740 0.420366 0.364554 0.409774

Floating Strike price¶

In [45]:
start_prices = np.arange(85, 116, 5)
results = []

for S0 in start_prices:
    # Generate paths with the current volatility
    CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
    CF_paths_df = pd.DataFrame(CF_paths).transpose()

    EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
    EM_paths_df = pd.DataFrame(EM_paths).transpose()

    MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs for the current volatility
    CF_payoffs = asian_option_param_float(CF_paths_df, 'monthly')
    EM_payoffs = asian_option_param_float(EM_paths_df, 'monthly')
    MS_payoffs = asian_option_param_float(MS_paths_df, 'monthly')

    # Append the results to the list
    results.append({"S0": S0, "Scheme": "Closed Form", **CF_payoffs})
    results.append({"S0": S0, "Scheme": "Euler-Maruyama", **EM_payoffs})
    results.append({"S0": S0, "Scheme": "Milstein", **MS_payoffs})

# Create the results DataFrame
ITM_OTM_results_df = pd.DataFrame(results)


# Display the DataFrame
print("Impact of changing starting stock prices:")
display(ITM_OTM_results_df)
Impact of changing starting stock prices:
S0 Scheme Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
0 85 Closed Form 5.013361 5.188780 4.986555 5.167597 2.857998 2.750780 2.835791 2.726877
1 85 Euler-Maruyama 5.014207 5.189499 4.987353 5.168143 2.858465 2.751101 2.836176 2.726994
2 85 Milstein 5.013066 5.188464 4.986262 5.167281 2.857776 2.750574 2.835571 2.726673
3 90 Closed Form 5.308265 5.494002 5.279882 5.471573 3.026115 2.912591 3.002602 2.887282
4 90 Euler-Maruyama 5.309161 5.494763 5.280726 5.472151 3.026610 2.912931 3.003010 2.887405
5 90 Milstein 5.307952 5.493668 5.279571 5.471239 3.025880 2.912373 3.002369 2.887066
6 95 Closed Form 5.603169 5.799225 5.573208 5.775549 3.194233 3.074401 3.169413 3.047687
7 95 Euler-Maruyama 5.604114 5.800028 5.574100 5.776160 3.194755 3.074760 3.169844 3.047817
8 95 Milstein 5.602838 5.798871 5.572881 5.775197 3.193985 3.074171 3.169167 3.047458
9 100 Closed Form 5.898072 6.104447 5.866535 6.079526 3.362350 3.236212 3.336224 3.208091
10 100 Euler-Maruyama 5.899067 6.105293 5.867474 6.080168 3.362900 3.236590 3.336678 3.208228
11 100 Milstein 5.897724 6.104075 5.866190 6.079154 3.362089 3.235970 3.335966 3.207851
12 105 Closed Form 6.192976 6.409670 6.159862 6.383502 3.530468 3.398023 3.503035 3.368496
13 105 Euler-Maruyama 6.194021 6.410557 6.160848 6.384176 3.531045 3.398419 3.503512 3.368639
14 105 Milstein 6.192611 6.409279 6.159500 6.383112 3.530194 3.397768 3.502764 3.368243
15 110 Closed Form 6.487879 6.714892 6.453189 6.687478 3.698585 3.559833 3.669847 3.528900
16 110 Euler-Maruyama 6.488974 6.715822 6.454221 6.688185 3.699190 3.560249 3.670346 3.529051
17 110 Milstein 6.487497 6.714483 6.452809 6.687070 3.698298 3.559567 3.669562 3.528636
18 115 Closed Form 6.782783 7.020114 6.746515 6.991454 3.866703 3.721644 3.836658 3.689305
19 115 Euler-Maruyama 6.783927 7.021086 6.747595 6.992193 3.867335 3.722078 3.837180 3.689462
20 115 Milstein 6.782383 7.019686 6.746119 6.991028 3.866403 3.721365 3.836360 3.689028

Appendix ¶

Visual impact of changing periods for discrete sampling ¶

In [46]:
# Define the frequency of periods
freq_periods = {
    'daily': 1,
    'weekly': 5,
    'biweekly': 10,
    'monthly': 21,
    'quarterly': 63,
    'semester': 126
}
In [47]:
# Inital assignment of parameters based on values provided in the exam

S0 = 100      # Today's stock price
T = 1         # Time (time to expiry)
sigma = 0.2   # Volatility
r = 0.05      # Constant risk-free interest rate (= mu in case of risk neutrality)
E = 100       # Strike

sims = 1
# We define the number of time steps based on number of working days in a year (as per Tutorial)
t_steps = 252
In [48]:
def create_plots(chosen_frequency):
    periods = freq_periods[chosen_frequency]

    # Run simulations
    CF_path = closed_form(S0, r, sigma, T, t_steps, sims)

    # Convert the lists of paths into DataFrames
    CF_path_df = pd.DataFrame(CF_path).transpose()

    # Calculate averages using the modified functions
    DA_CF_df = discrete_arithmetic_avrg(CF_path_df, periods)
    DG_CF_df = discrete_geometric_avrg(CF_path_df, periods)
    
    # Calculate the discrete average value by taking the mean of all the averages
    DA_CF_mean = DA_CF_df.mean()[0]
    DG_CF_mean = DG_CF_df.mean()[0]

    fig1 = go.Figure()  # Discrete Averages Plot

    # Stock price
    fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=CF_path[0], mode='lines', name='CF_price'))
    
    # Discrete plot
    discrete_path = discrete_stock_path(CF_path_df[0], periods)
    fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=discrete_path, mode='lines',
                          name='Discrete_path', line=dict(color='purple')))
    
    # Closed-Form
    fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=DA_CF_df[0], mode='lines', name='DA'))
    fig1.add_trace(go.Scatter(x=list(range(t_steps)), y=DG_CF_df[0], mode='lines', name='DG'))

    # Add horizontal dashed lines for the discrete mean
    fig1.add_shape(type='line', x0=0, x1=t_steps-1, y0=DA_CF_mean, y1=DA_CF_mean,
                   yref='y', xref='x', line=dict(color='red', dash='dash'))
    fig1.add_shape(type='line', x0=0, x1=t_steps-1, y0=DG_CF_mean, y1=DG_CF_mean,
                   yref='y', xref='x', line=dict(color='green', dash='dash'))

    # Add legend entries for the dashed lines
    fig1.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='red', dash='dash'),
                              name='Discrete Arithmetic Mean'))
    fig1.add_trace(go.Scatter(x=[None], y=[None], mode='lines', line=dict(color='green', dash='dash'),
                              name='Discrete Geometric Mean'))

    fig1.update_layout(title='Discrete Averages', xaxis_title='Time steps', yaxis_title='Stock Price')

    fig1.show()
In [49]:
create_plots('daily')
In [50]:
create_plots('weekly')
In [51]:
create_plots('biweekly')
In [52]:
create_plots('monthly')
In [53]:
create_plots('quarterly')
In [54]:
create_plots('semester')

Payoff and computation impact of changing the number of discrete time periods ¶

In [55]:
sims = 3000
# Generate paths and measure time
CF_paths = closed_form(S0, r, sigma, T, t_steps, sims)
EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)

# Convert the lists of paths into DataFrames
CF_paths_df = pd.DataFrame(CF_paths).transpose()
EM_paths_df = pd.DataFrame(EM_paths).transpose()
MS_paths_df = pd.DataFrame(MS_paths).transpose()
In [56]:
# Calculate payoffs and measure time for daily frequency
CF_payoffs_daily, CF_avg_times_daily, CF_payoffs_time_daily = measure_time(asian_option_payoffs, CF_paths_df, E, 'daily')
EM_payoffs_daily, EM_avg_times_daily, EM_payoffs_time_daily = measure_time(asian_option_payoffs, EM_paths_df, E, 'daily')
MS_payoffs_daily, MS_avg_times_daily, MS_payoffs_time_daily = measure_time(asian_option_payoffs, MS_paths_df, E, 'daily')

# Calculate payoffs and measure time for weekly frequency
CF_payoffs_weekly, CF_avg_times_weekly, CF_payoffs_time_weekly = measure_time(asian_option_payoffs, CF_paths_df, E, 'weekly')
EM_payoffs_weekly, EM_avg_times_weekly, EM_payoffs_time_weekly = measure_time(asian_option_payoffs, EM_paths_df, E, 'weekly')
MS_payoffs_weekly, MS_avg_times_weekly, MS_payoffs_time_weekly = measure_time(asian_option_payoffs, MS_paths_df, E, 'weekly')

# Calculate payoffs and measure time for biweekly frequency
CF_payoffs_biweekly, CF_avg_times_biweekly, CF_payoffs_time_biweekly = measure_time(asian_option_payoffs, CF_paths_df, E, 'biweekly')
EM_payoffs_biweekly, EM_avg_times_biweekly, EM_payoffs_time_biweekly = measure_time(asian_option_payoffs, EM_paths_df, E, 'biweekly')
MS_payoffs_biweekly, MS_avg_times_biweekly, MS_payoffs_time_biweekly = measure_time(asian_option_payoffs, MS_paths_df, E, 'biweekly')

# Calculate payoffs and measure time for monthly frequency
CF_payoffs_monthly, CF_avg_times_monthly, CF_payoffs_time_monthly = measure_time(asian_option_payoffs, CF_paths_df, E, 'monthly')
EM_payoffs_monthly, EM_avg_times_monthly, EM_payoffs_time_monthly = measure_time(asian_option_payoffs, EM_paths_df, E, 'monthly')
MS_payoffs_monthly, MS_avg_times_monthly, MS_payoffs_time_monthly = measure_time(asian_option_payoffs, MS_paths_df, E, 'monthly')

# Calculate payoffs and measure time for quarterly frequency
CF_payoffs_quarterly, CF_avg_times_quarterly, CF_payoffs_time_quarterly = measure_time(asian_option_payoffs, CF_paths_df, E, 'quarterly')
EM_payoffs_quarterly, EM_avg_times_quarterly, EM_payoffs_time_quarterly = measure_time(asian_option_payoffs, EM_paths_df, E, 'quarterly')
MS_payoffs_quarterly, MS_avg_times_quarterly, MS_payoffs_time_quarterly = measure_time(asian_option_payoffs, MS_paths_df, E, 'quarterly')

# Calculate payoffs and measure time for semester frequency
CF_payoffs_semester, CF_avg_times_semester, CF_payoffs_time_semester = measure_time(asian_option_payoffs, CF_paths_df, E, 'semester')
EM_payoffs_semester, EM_avg_times_semester, EM_payoffs_time_semester = measure_time(asian_option_payoffs, EM_paths_df, E, 'semester')
MS_payoffs_semester, MS_avg_times_semester, MS_payoffs_time_semester = measure_time(asian_option_payoffs, MS_paths_df, E, 'semester')
In [57]:
# Combine the payoffs into a DataFrame
payoffs_df = pd.DataFrame({'Closed Form Daily': CF_payoffs_daily,
                           'Euler-Maruyama Daily': EM_payoffs_daily,
                           'Milstein Daily': MS_payoffs_daily,
                           'Closed Form Weekly': CF_payoffs_weekly,
                           'Euler-Maruyama Weekly': EM_payoffs_weekly,
                           'Milstein Weekly': MS_payoffs_weekly,
                           'Closed Form Biweekly': CF_payoffs_biweekly,
                           'Euler-Maruyama Biweekly': EM_payoffs_biweekly,
                           'Milstein Biweekly': MS_payoffs_biweekly,
                           'Closed Form Monthly': CF_payoffs_monthly,
                           'Euler-Maruyama Monthly': EM_payoffs_monthly,
                           'Milstein Monthly': MS_payoffs_monthly,
                           'Closed Form Quarterly': CF_payoffs_quarterly,
                           'Euler-Maruyama Quarterly': EM_payoffs_quarterly,
                           'Milstein Quarterly': MS_payoffs_quarterly,
                           'Closed Form Semester': CF_payoffs_semester,
                           'Euler-Maruyama Semester': EM_payoffs_semester,
                           'Milstein Semester': MS_payoffs_semester
                           }).transpose()

# Display the table
payoffs_df
Out[57]:
Call_CA Call_CG Call_DA Call_DG Put_CA Put_CG Put_DA Put_DG
Closed Form Daily 5.972005 5.742982 5.972005 5.742982 3.291008 3.409279 3.291008 3.409279
Euler-Maruyama Daily 5.971964 5.743228 5.971964 5.743228 3.287663 3.406148 3.287663 3.406148
Milstein Daily 5.970964 5.742027 5.970964 5.742027 3.290267 3.408488 3.290267 3.408488
Closed Form Weekly 5.972005 5.742982 5.927650 5.696655 3.291008 3.409279 3.263791 3.382654
Euler-Maruyama Weekly 5.971964 5.743228 5.927628 5.696950 3.287663 3.406148 3.260462 3.379565
Milstein Weekly 5.970964 5.742027 5.926616 5.695709 3.290267 3.408488 3.263056 3.381869
Closed Form Biweekly 5.972005 5.742982 5.905184 5.669475 3.291008 3.409279 3.241841 3.362443
Euler-Maruyama Biweekly 5.971964 5.743228 5.905397 5.669749 3.287663 3.406148 3.238750 3.359332
Milstein Biweekly 5.970964 5.742027 5.904156 5.668536 3.290267 3.408488 3.241111 3.361663
Closed Form Monthly 5.972005 5.742982 5.894104 5.646514 3.291008 3.409279 3.208988 3.333806
Euler-Maruyama Monthly 5.971964 5.743228 5.894126 5.646688 3.287663 3.406148 3.205987 3.330861
Milstein Monthly 5.970964 5.742027 5.893078 5.645582 3.290267 3.408488 3.208263 3.333029
Closed Form Quarterly 5.972005 5.742982 5.752753 5.473459 3.291008 3.409279 3.056623 3.193749
Euler-Maruyama Quarterly 5.971964 5.743228 5.752851 5.473910 3.287663 3.406148 3.054169 3.191528
Milstein Quarterly 5.970964 5.742027 5.751754 5.472564 3.290267 3.408488 3.055927 3.192995
Closed Form Semester 5.972005 5.742982 5.604054 5.285136 3.291008 3.409279 2.926096 3.074052
Euler-Maruyama Semester 5.971964 5.743228 5.603883 5.285251 3.287663 3.406148 2.923868 3.071927
Milstein Semester 5.970964 5.742027 5.603088 5.284287 3.290267 3.408488 2.925430 3.073322
In [58]:
# Create dictionary for each frequency
daily_times_DG = {"Closed Form Daily": CF_avg_times_daily["DG_time"],
                  "Euler-Maruyama Daily": EM_avg_times_daily["DG_time"],
                  "Milstein Daily": MS_avg_times_daily["DG_time"]}
daily_times_DA = {"Closed Form Daily": CF_avg_times_daily["DA_time"],
                  "Euler-Maruyama Daily": EM_avg_times_daily["DA_time"],
                  "Milstein Daily": MS_avg_times_daily["DA_time"]}

# Weekly
weekly_times_DG = {"Closed Form Weekly": CF_avg_times_weekly["DG_time"],
                   "Euler-Maruyama Weekly": EM_avg_times_weekly["DG_time"],
                   "Milstein Weekly": MS_avg_times_weekly["DG_time"]}
weekly_times_DA = {"Closed Form Weekly": CF_avg_times_weekly["DA_time"],
                   "Euler-Maruyama Weekly": EM_avg_times_weekly["DA_time"],
                   "Milstein Weekly": MS_avg_times_weekly["DA_time"]}

# Biweekly
biweekly_times_DG = {"Closed Form Biweekly": CF_avg_times_biweekly["DG_time"],
                     "Euler-Maruyama Biweekly": EM_avg_times_biweekly["DG_time"],
                     "Milstein Biweekly": MS_avg_times_biweekly["DG_time"]}
biweekly_times_DA = {"Closed Form Biweekly": CF_avg_times_biweekly["DA_time"],
                     "Euler-Maruyama Biweekly": EM_avg_times_biweekly["DA_time"],
                     "Milstein Biweekly": MS_avg_times_biweekly["DA_time"]}

# Monthly
monthly_times_DG = {"Closed Form Monthly": CF_avg_times_monthly["DG_time"],
                    "Euler-Maruyama Monthly": EM_avg_times_monthly["DG_time"],
                    "Milstein Monthly": MS_avg_times_monthly["DG_time"]}
monthly_times_DA = {"Closed Form Monthly": CF_avg_times_monthly["DA_time"],
                    "Euler-Maruyama Monthly": EM_avg_times_monthly["DA_time"],
                    "Milstein Monthly": MS_avg_times_monthly["DA_time"]}

# Quarterly
quarterly_times_DG = {"Closed Form Quarterly": CF_avg_times_quarterly["DG_time"],
                      "Euler-Maruyama Quarterly": EM_avg_times_quarterly["DG_time"],
                      "Milstein Quarterly": MS_avg_times_quarterly["DG_time"]}
quarterly_times_DA = {"Closed Form Quarterly": CF_avg_times_quarterly["DA_time"],
                      "Euler-Maruyama Quarterly": EM_avg_times_quarterly["DA_time"],
                      "Milstein Quarterly": MS_avg_times_quarterly["DA_time"]}

# Semester
semester_times_DG = {"Closed Form Semester": CF_avg_times_semester["DG_time"],
                     "Euler-Maruyama Semester": EM_avg_times_semester["DG_time"],
                     "Milstein Semester": MS_avg_times_semester["DG_time"]}
semester_times_DA = {"Closed Form Semester": CF_avg_times_semester["DA_time"],
                     "Euler-Maruyama Semester": EM_avg_times_semester["DA_time"],
                     "Milstein Semester": MS_avg_times_semester["DA_time"]}


# Combine dictionaries
all_times_DG = {**daily_times_DG, **weekly_times_DG, **biweekly_times_DG,
                **monthly_times_DG, **quarterly_times_DG, **semester_times_DG}
all_times_DA = {**daily_times_DA, **weekly_times_DA, **biweekly_times_DA,
                **monthly_times_DA, **quarterly_times_DA, **semester_times_DA}

# Create DataFrame from dictionaries
running_times_df = pd.DataFrame({"Payoff DG": all_times_DG, "Payoff DA": all_times_DA})

# Display computation time
running_times_df
Out[58]:
Payoff DG Payoff DA
Closed Form Daily 9.764829 9.884471
Euler-Maruyama Daily 9.875819 9.895697
Milstein Daily 9.991761 10.948786
Closed Form Weekly 2.913063 2.628983
Euler-Maruyama Weekly 2.765884 2.615262
Milstein Weekly 2.560906 2.598423
Closed Form Biweekly 1.580553 1.555410
Euler-Maruyama Biweekly 1.540514 1.475310
Milstein Biweekly 1.594928 1.704879
Closed Form Monthly 1.006115 0.977967
Euler-Maruyama Monthly 1.000391 0.995500
Milstein Monthly 0.981673 0.972562
Closed Form Quarterly 0.723650 0.678224
Euler-Maruyama Quarterly 0.683528 0.638352
Milstein Quarterly 0.624613 0.806233
Closed Form Semester 0.532741 0.533636
Euler-Maruyama Semester 0.531425 0.526989
Milstein Semester 0.528666 0.524930

Impact of changing number of simulations ¶

In [59]:
def run_simulation(sims):
    # Generate paths and measure time
    start_time_CF = time.time()
    closed_form_paths = closed_form(S0, r, sigma, T, t_steps, sims)
    CF_time = time.time() - start_time_CF

    start_time_EM = time.time()
    EM_paths = euler_maruyama(S0, r, sigma, T, t_steps, sims)
    EM_time = time.time() - start_time_EM

    start_time_MS = time.time()
    MS_paths = milstein_paths(S0, r, sigma, T, t_steps, sims)
    MS_time = time.time() - start_time_MS

    # Convert the lists of paths into DataFrames
    CF_paths_df = pd.DataFrame(closed_form_paths).transpose()
    EM_paths_df = pd.DataFrame(EM_paths).transpose()
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs and measure time
    CF_payoffs, _, _ = measure_time(asian_option_payoffs, CF_paths_df, E, 'monthly')
    EM_payoffs, _, _ = measure_time(asian_option_payoffs, EM_paths_df, E, 'monthly')
    MS_payoffs, _, _ = measure_time(asian_option_payoffs, MS_paths_df, E, 'monthly')

    return {
        'Closed Form': {'Call': CF_payoffs['Call_CG'], 'Put': CF_payoffs['Put_CG']},
        'Euler-Maruyama': {'Call': EM_payoffs['Call_CG'], 'Put': EM_payoffs['Put_CG']},
        'Milstein': {'Call': MS_payoffs['Call_CG'], 'Put': MS_payoffs['Put_CG']}
    }
In [60]:
# Run the loop with increasing number of simulations
simulations = list(range(1000, 31001, 2000))
results_call = []
results_put = []

for sims in simulations:
    result = run_simulation(sims)
    results_call.append({'Simulations': sims,
                         'Closed Form': result['Closed Form']['Call'],
                         'Euler-Maruyama': result['Euler-Maruyama']['Call'],
                         'Milstein': result['Milstein']['Call']})
    
    results_put.append({'Simulations': sims,
                        'Closed Form': result['Closed Form']['Put'],
                        'Euler-Maruyama': result['Euler-Maruyama']['Put'],
                        'Milstein': result['Milstein']['Put']})

# Create the results DataFrames
call_results_df = pd.DataFrame(results_call)
put_results_df = pd.DataFrame(results_put)

# Display the call options table
print("Call Options:")
display(call_results_df)

# Display the put options table
print("\nPut Options:")
display(put_results_df)
Call Options:
Simulations Closed Form Euler-Maruyama Milstein
0 1000 5.935308 5.936440 5.934297
1 3000 5.742982 5.743228 5.742027
2 5000 5.543706 5.544363 5.542789
3 7000 5.510057 5.510205 5.509146
4 9000 5.517675 5.516971 5.516762
5 11000 5.531744 5.530984 5.530825
6 13000 5.536710 5.536015 5.535788
7 15000 5.492669 5.492384 5.491758
8 17000 5.503631 5.503513 5.502718
9 19000 5.473138 5.473157 5.472230
10 21000 5.467535 5.467586 5.466627
11 23000 5.479061 5.478997 5.478151
12 25000 5.472565 5.472421 5.471655
13 27000 5.469883 5.469790 5.468974
14 29000 5.460992 5.460786 5.460082
15 31000 5.456691 5.456415 5.455783
Put Options:
Simulations Closed Form Euler-Maruyama Milstein
0 1000 3.446087 3.444558 3.445297
1 3000 3.409279 3.406148 3.408488
2 5000 3.466096 3.462880 3.465290
3 7000 3.483877 3.481911 3.483063
4 9000 3.494728 3.492468 3.493912
5 11000 3.496486 3.494676 3.495670
6 13000 3.474224 3.472958 3.473412
7 15000 3.464928 3.463950 3.464116
8 17000 3.454000 3.452824 3.453190
9 19000 3.463201 3.462231 3.462389
10 21000 3.463745 3.462672 3.462934
11 23000 3.457536 3.456684 3.456726
12 25000 3.463316 3.462383 3.462504
13 27000 3.450246 3.449525 3.449435
14 29000 3.458452 3.457832 3.457639
15 31000 3.465743 3.464949 3.464930
In [61]:
# Create a trace for each scheme
closed_form_trace = go.Scatter(
    x=call_results_df['Simulations'],
    y=call_results_df['Closed Form'],
    mode='lines',
    name='Closed Form'
)

euler_maruyama_trace = go.Scatter(
    x=call_results_df['Simulations'],
    y=call_results_df['Euler-Maruyama'],
    mode='lines',
    name='Euler-Maruyama'
)

milstein_trace = go.Scatter(
    x=call_results_df['Simulations'],
    y=call_results_df['Milstein'],
    mode='lines',
    name='Milstein'
)

# Create a layout for the plot
layout = go.Layout(
    title='Comparison of Call Option Payoffs for different schemes and simulation values',
    xaxis=dict(title='Simulations'),
    yaxis=dict(title='Option Price')
)

# Create a Figure object
fig = go.Figure(data=[closed_form_trace, euler_maruyama_trace, milstein_trace], layout=layout)

# Plot the figure
fig.show()
In [62]:
# Create a trace for each scheme
closed_form_trace = go.Scatter(
    x=put_results_df['Simulations'],
    y=put_results_df['Closed Form'],
    mode='lines',
    name='Closed Form'
)

euler_maruyama_trace = go.Scatter(
    x=put_results_df['Simulations'],
    y=put_results_df['Euler-Maruyama'],
    mode='lines',
    name='Euler-Maruyama'
)

milstein_trace = go.Scatter(
    x=put_results_df['Simulations'],
    y=put_results_df['Milstein'],
    mode='lines',
    name='Milstein'
)

# Create a layout for the plot
layout = go.Layout(
    title='Comparison of Call Option Payoffs for different schemes and simulation values',
    xaxis=dict(title='Simulations'),
    yaxis=dict(title='Option Price')
)

# Create a Figure object
fig = go.Figure(data=[closed_form_trace, euler_maruyama_trace, milstein_trace], layout=layout)

# Plot the figure
fig.show()
In [63]:
# Update measure_time due to differences in appendix and debugging issues
def measure_time2(method, *args):
    start_time = time.time()
    result = method(*args)
    end_time = time.time()
    elapsed_time = end_time - start_time
    return result, elapsed_time
In [64]:
def run_simulation_time(sims):
    # Generate paths and measure time
    CF_paths, CF_path_time = measure_time2(closed_form, S0, r, sigma, T, t_steps, sims)
    EM_paths, EM_path_time = measure_time2(euler_maruyama, S0, r, sigma, T, t_steps, sims)
    MS_paths, MS_path_time = measure_time2(milstein_paths, S0, r, sigma, T, t_steps, sims)

    # Convert the lists of paths into DataFrames
    CF_paths_df = pd.DataFrame(CF_paths).transpose()
    EM_paths_df = pd.DataFrame(EM_paths).transpose()
    MS_paths_df = pd.DataFrame(MS_paths).transpose()

    # Calculate payoffs and measure time
    CF_payoffs, CF_payoff_time = measure_time2(asian_option_payoffs, CF_paths_df, E, 'monthly')
    EM_payoffs, EM_payoff_time = measure_time2(asian_option_payoffs, EM_paths_df, E, 'monthly')
    MS_payoffs, MS_payoff_time = measure_time2(asian_option_payoffs, MS_paths_df, E, 'monthly')

    return {
        'Closed Form': {'Path': CF_path_time, 'Payoff': CF_payoff_time},
        'Euler-Maruyama': {'Path': EM_path_time, 'Payoff': EM_payoff_time},
        'Milstein': {'Path': MS_path_time, 'Payoff': MS_payoff_time}
    }
In [66]:
# Define list of number of simulations
simulations = list(range(1000, 31001, 2000))

# Initialize results dictionary
results_time = {
    "Scheme": [],
    "Num_Sims": [],
    "Path_Time": [],
    "Payoff_Time": []
}

# Loop through schemes
for scheme in ["Closed Form", "Euler-Maruyama", "Milstein Scheme"]:
    
    # Loop through number of simulations
    for num_sims in simulations:

        # Generate paths and measure time
        start_time_paths = time.time()
        if scheme == "Closed Form":
            paths = closed_form(S0, r, sigma, T, t_steps, num_sims)
        elif scheme == "Euler-Maruyama":
            paths = euler_maruyama(S0, r, sigma, T, t_steps, num_sims)
        elif scheme == "Milstein Scheme":
            paths = milstein_paths(S0, r, sigma, T, t_steps, num_sims)
        time_paths = time.time() - start_time_paths

        # Convert the list of paths into a DataFrame
        paths_df = pd.DataFrame(paths).transpose()

        # Calculate payoffs and measure time
        payoffs, _, payoffs_time = measure_time(asian_option_payoffs,
                                                paths_df, E, 'monthly')

        # Add computation times to results dictionary
        results_time["Scheme"].append(scheme)
        results_time["Num_Sims"].append(num_sims)
        results_time["Path_Time"].append(time_paths)
        results_time["Payoff_Time"].append(payoffs_time)

# Convert dictionary to DataFrame
results_time_df = pd.DataFrame(results_time)

# Display results
results_time_df
Out[66]:
Scheme Num_Sims Path_Time Payoff_Time
0 Closed Form 1000 0.020747 0.806065
1 Closed Form 3000 0.057919 2.759581
2 Closed Form 5000 0.108932 4.841202
3 Closed Form 7000 0.119634 6.426707
4 Closed Form 9000 0.159325 8.645734
5 Closed Form 11000 0.203289 10.922940
6 Closed Form 13000 0.223321 13.016799
7 Closed Form 15000 0.263769 17.260853
8 Closed Form 17000 0.294461 18.181353
9 Closed Form 19000 0.327890 22.107067
10 Closed Form 21000 0.370890 25.375048
11 Closed Form 23000 0.404594 27.897599
12 Closed Form 25000 0.444528 30.704906
13 Closed Form 27000 0.470181 34.128994
14 Closed Form 29000 0.502675 41.517889
15 Closed Form 31000 0.553803 42.016981
16 Euler-Maruyama 1000 0.126859 0.791518
17 Euler-Maruyama 3000 0.325393 2.485236
18 Euler-Maruyama 5000 0.553342 4.323902
19 Euler-Maruyama 7000 0.795626 6.306914
20 Euler-Maruyama 9000 1.002682 8.392611
21 Euler-Maruyama 11000 1.277739 10.906729
22 Euler-Maruyama 13000 1.559721 13.074393
23 Euler-Maruyama 15000 1.734908 15.865952
24 Euler-Maruyama 17000 2.025306 18.629022
25 Euler-Maruyama 19000 2.195186 23.076041
26 Euler-Maruyama 21000 2.439029 26.219109
27 Euler-Maruyama 23000 2.632916 28.231555
28 Euler-Maruyama 25000 2.880464 31.748446
29 Euler-Maruyama 27000 3.170544 34.466074
30 Euler-Maruyama 29000 3.313994 38.334749
31 Euler-Maruyama 31000 3.697275 42.244707
32 Milstein Scheme 1000 0.368667 0.795424
33 Milstein Scheme 3000 0.765978 2.505959
34 Milstein Scheme 5000 1.343879 4.375535
35 Milstein Scheme 7000 1.837654 6.324358
36 Milstein Scheme 9000 2.311975 8.453134
37 Milstein Scheme 11000 2.791854 10.671279
38 Milstein Scheme 13000 3.381962 13.261419
39 Milstein Scheme 15000 3.862129 16.024965
40 Milstein Scheme 17000 4.493155 18.881933
41 Milstein Scheme 19000 5.099248 21.288825
42 Milstein Scheme 21000 5.609810 24.476984
43 Milstein Scheme 23000 6.133127 27.489787
44 Milstein Scheme 25000 6.486272 30.998736
45 Milstein Scheme 27000 7.130600 34.122734
46 Milstein Scheme 29000 7.595885 38.635016
47 Milstein Scheme 31000 8.399595 42.425363
In [67]:
# Create an empty DataFrame with the required columns
computation_time_df = pd.DataFrame(columns=['Simulation', 'Scheme', 'Path Generation', 'Payoff Calculation'])

# Loop through the results dataframe and extract relevant values
for i in range(len(results_time_df)):
    scheme = results_time_df.loc[i, 'Scheme']
    simulation = results_time_df.loc[i, 'Num_Sims']
    path_generation_time = results_time_df.loc[i, 'Path_Time']
    payoff_calculation_time = results_time_df.loc[i, 'Payoff_Time']
    
    computation_time_data = {
        'Scheme': scheme,
        'Simulation': simulation,
        'Path Generation': path_generation_time,
        'Payoff Calculation': payoff_calculation_time
    }
        
    computation_time_df = computation_time_df.append(computation_time_data, ignore_index=True)
    
# Display results
#computation_time_df
In [68]:
closed_form_times = computation_time_df[computation_time_df['Scheme'] == 'Closed Form']
euler_maruyama_times = computation_time_df[computation_time_df['Scheme'] == 'Euler-Maruyama']
milstein_times = computation_time_df[computation_time_df['Scheme'] == 'Milstein Scheme']
In [69]:
merged_times = closed_form_times.merge(euler_maruyama_times, on='Simulation', suffixes=('_CF', '_EM'))
merged_times = merged_times.merge(milstein_times, on='Simulation', suffixes=('_EM', '_MS'))
In [70]:
# Reorder the columns
merged_times = merged_times[['Simulation', 'Path Generation_CF', 'Payoff Calculation_CF', 'Path Generation_EM', 'Payoff Calculation_EM',
                             'Path Generation', 'Payoff Calculation']]

# Rename the columns
merged_times.columns = ['Simulation', 'Path_CF', 'Payoff_CF', 'Path_EM', 'Payoff_EM', 'Path_MS', 'Payoff_MS']

# Display the merged dataframe
merged_times
Out[70]:
Simulation Path_CF Payoff_CF Path_EM Payoff_EM Path_MS Payoff_MS
0 1000 0.020747 0.806065 0.126859 0.791518 0.368667 0.795424
1 3000 0.057919 2.759581 0.325393 2.485236 0.765978 2.505959
2 5000 0.108932 4.841202 0.553342 4.323902 1.343879 4.375535
3 7000 0.119634 6.426707 0.795626 6.306914 1.837654 6.324358
4 9000 0.159325 8.645734 1.002682 8.392611 2.311975 8.453134
5 11000 0.203289 10.922940 1.277739 10.906729 2.791854 10.671279
6 13000 0.223321 13.016799 1.559721 13.074393 3.381962 13.261419
7 15000 0.263769 17.260853 1.734908 15.865952 3.862129 16.024965
8 17000 0.294461 18.181353 2.025306 18.629022 4.493155 18.881933
9 19000 0.327890 22.107067 2.195186 23.076041 5.099248 21.288825
10 21000 0.370890 25.375048 2.439029 26.219109 5.609810 24.476984
11 23000 0.404594 27.897599 2.632916 28.231555 6.133127 27.489787
12 25000 0.444528 30.704906 2.880464 31.748446 6.486272 30.998736
13 27000 0.470181 34.128994 3.170544 34.466074 7.130600 34.122734
14 29000 0.502675 41.517889 3.313994 38.334749 7.595885 38.635016
15 31000 0.553803 42.016981 3.697275 42.244707 8.399595 42.425363

Andreas Mavrocordatos | | LinkedIn